/*=+--+=#=+--     Unmanned Aerial System Management Software      --+=#=+--+=#*\
|          Copyright (C) 2011 Regents of the University of Colorado.           |
|                             All Rights Reserved.                             |

     This program is free software: you can redistribute it and/or modify
     it under the terms of the GNU General Public License version 2 as
     published by the Free Software Foundation.

     This program is distributed in the hope that it will be useful,
     but WITHOUT ANY WARRANTY; without even the implied warranty of
     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     GNU General Public License for more details.

     You should have received a copy of the GNU General Public License
     along with this program.  If not, see <http://www.gnu.org/licenses/>.

            Jack Elston                       Cory Dixon                        
|           elstonj@colorado.edu              dixonc@colorado.edu              |
|                                                                              |
\*=+--+=#=+--                 --+=#=+--+=#=+--                    --+=#=+--+=#*/

/***********************************************************************
 *			
 * FILENAME:
 * fileLog.cxx
 *
 * PURPOSE:
 * This code implements a simple log object to log data
 * to a specific file type.
 *  
 * CREATED:
 * 07/28/2002 by Cory Dixon
 *
 * LAST MODIFIED:
 * $Author: dixonc $
 * $Date: 2005/07/13 21:00:06 $
 * $Revision: 1.4 $
 *
 ***********************************************************************/

// standard includes
#if defined( VXWORKS )
# include "vxWorks.h"
# include "sysLib.h"
# include "ioLib.h"
#endif

#if !defined( WIN32 )
# include <errno.h>
# include <fcntl.h>
# include <unistd.h>
# include <sys/types.h>          // for open(), read(), write(), close()
# include <sys/stat.h>           // for open(), read(), write(), close()
#endif

#include <stdio.h>

#include "fileLog.h"

#define LOG_EXT_STRING "log"
#define LOG_EXT_LENGTH 4 	// 1 for the '.'

String timeStr( const char *format = "%a-%b-%d-%Y %T" ) {
	char buf[ 256 ];
	time_t tt = ::time(NULL);
	(void)strftime(buf, 255, format, localtime((time_t *)&tt) ); 
	return String(buf);
}

// must use if writing/logging
FileLog::FileLog( String dev, FileLogMode mode)
: fileName(NULL), baseName(dev)
{
	Fd         = -1;
	bytesW     = 0;
	fileS      = 0;
	logMode    = mode;

	// track SVN version number so we can decode the file log
	svn_ver      = -1;
	compiled_ver = atoi( SVN_VER );
	if( baseName.length() > 0 ) {
		char last = baseName( baseName.length() );
		if( last != '/' ) baseName += "/";
	}
}


FileLog::~FileLog()
{
	if(Fd != -1)
		if( !close() )
			cout << "FileLog::~FileLog - could not close file" << endl;
}


//----------------------------------------------------------
// Open/Close the log file
//----------------------------------------------------------
bool FileLog::open()
{    
	struct stat fileStat;
	int flags = O_WRONLY | O_CREAT | O_NONBLOCK;

#if defined(_MSC_VER) || defined(__MINGW32__) || defined(VXWORKS)
	int mode = 00666;
#else
	mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
#endif


	if( logMode == READ) {
		return FileLog::open( baseName + String(".") + String(LOG_EXT_STRING) );
	}
	else if( logMode == CREATE ) {
		flags |= O_TRUNC;

		fileName = baseName + timeStr() + String(".") + String(LOG_EXT_STRING);
	}
	else if( logMode == APPEND ) {
		flags |= O_APPEND;

		fileName = baseName + String(".") + String(LOG_EXT_STRING);

		if( stat(fileName.c_str(), &fileStat) == ERROR )
			fileS = 0;
		else
			fileS = (int)fileStat.st_size;
	}
	else if( logMode == INCREMENT ) {
		int count = 0;
		int countLength = 0;
		char numBuf[10] = "";

		do {
			count++;
			countLength = (count / 10) + 2; // 1 for the '_'

			(void)sprintf( numBuf, "_%d.", count);

			fileName = baseName + String(numBuf) + String(LOG_EXT_STRING);

		} while( stat(fileName.c_str(), &fileStat) == OK );

		flags |= O_EXCL;
	}

	Fd = ::open( fileName.c_str(), flags, mode );
	if ( Fd == ERROR ) {
		cout << "FileLog::open - error opening " <<  fileName << endl;
		return false;
	}

	// write in time stamp header,
	// line starts with -1.0 as first four bytes
	int nwrite;
	float32_t startVal = -1;
	if( (nwrite = ::write(Fd, (char *)&startVal, 4)) != 4 ) {
		perror("FileLog::open() - ::write");
		return false;
	}
	fileS += nwrite;

	// then the next n bytes is a struct tm
	struct tm *tPtr;
	time_t tt = ::time(NULL);

	// get local time
	tPtr = localtime( &tt );
	tmTime = *tPtr;
	if( (nwrite = ::write(Fd, (char *)&tmTime, sizeof(struct tm))) != sizeof(struct tm) )
		return ERROR;
	fileS += nwrite;

	// set svn version number as compiled version
	// line starts with -2.0 as first four bytes
	svn_ver = compiled_ver;
	startVal = -2;
	if( (nwrite = ::write(Fd, (char *)&startVal, 4)) != 4 ) {
		perror("FileLog::open() - ::write");
		return false;
	}
	fileS += nwrite;
	if( (nwrite = ::write(Fd, (char *)&svn_ver, 4)) != 4 ) {
		perror("FileLog::open() - ::write");
		return false;
	}
	fileS += nwrite;

	return true;
}


bool FileLog::open( const String & name )
{    
	struct stat fileStat;
	int flags = O_RDONLY | O_NONBLOCK;

	if( name != "" ) {
		fileName = name;
	}

#if defined(_MSC_VER) || defined(__MINGW32__)
	int mode = 00666;
#else
	mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
#endif

	if( stat(fileName.c_str(), &fileStat) == ERROR ) {
		cout << "FileLog::stat - error opening " <<  fileName << endl;
		return false;
	}
	else
		fileS = (int)fileStat.st_size;

	Fd = ::open( fileName.c_str(), flags, mode );
	if ( Fd == ERROR ) {
		return false;
	}

	return true;
}

bool FileLog::close()
{
	bool status = true;

	if(Fd < 0)
		return status;

#if defined( WIN32 )
	CloseHandle( Fd );
#else
	if( ::close(Fd) < 0)
		status = false;
#endif

	Fd = -1;

	return status;
}

int FileLog::write( in_addr from, in_addr to, const char * buf, uint16_t buf_len )
{
	int nwrite=-1;
	float32_t timeVal;
	char *timePtr = (char *)&timeVal;
	char *countPtr = (char *)&buf_len;

	// check given buffer
	if( buf == NULL ) //|| buf_len < 0 )
		return ERROR;

	// check Status of file
	if( Fd <= 0 ){
		cout << "FileLog::write - file not open" << endl;
		return ERROR;
	}

	// get the current time
	ts.stamp();
	timeVal = ts.secOfDay();

	// make the time stamp first 4 bytes in the line 
	if( (nwrite = ::write(Fd, timePtr, 4)) != 4 )
	{
#if ( DEBUG_PRINT >= ERRDEBUG )
		perror("FileLog::write - couldn't write header");
#endif
		return ERROR;
	}
	fileS += nwrite;

	// add from, to fields
	fileS += ::write(Fd, &(from.s_addr), 4);
	fileS += ::write(Fd, &(to.s_addr), 4);

	// make the count of the number of bytes in the line
	// the next 2 bytes
	if( (nwrite = ::write(Fd, countPtr, 2)) != 2)
	{
#if ( DEBUG_PRINT >= ERRDEBUG )
		perror("FileLog::write - couldn't write count");
#endif
		return ERROR;
	}
	fileS += nwrite;

	// make the next the bytes the next buf_len of bytes
	// in the file
	nwrite = ::write(Fd, (char *)buf, buf_len);    
	if( nwrite > 0)
		bytesW += nwrite;
#if ( DEBUG_PRINT >= ERRDEBUG )
	else if ( nwrite < 0 ) {
		perror("FileLog::write - couldn't write buffer");
	}
#endif

	return nwrite;
}

//-------------------------------------------------------------------- 
// Write Functions
// - the return value is not the total written to the file, but should
//   reflect buf_len, the number of bytes requested
//-------------------------------------------------------------------- 
int FileLog::write( int from, int to, const char * buf, uint16_t buf_len )
{
	int nwrite=-1;
	float32_t timeVal;
	char *timePtr = (char *)&timeVal;
	char *countPtr = (char *)&buf_len;

	// check given buffer
	if( buf == NULL ) //|| buf_len < 0 )
		return ERROR;

	// check Status of file
	if( Fd <= 0 ){
		cout << "FileLog::write - file not open" << endl;
		return ERROR;
	}

	// get the current time
	ts.stamp();
	timeVal = ts.secOfDay();

	// make the time stamp first 4 bytes in the line 
	if( (nwrite = ::write(Fd, timePtr, 4)) != 4 )
	{
#if ( DEBUG_PRINT >= ERRDEBUG )
		perror("FileLog::write - couldn't write header");
#endif
		return ERROR;
	}
	fileS += nwrite;

	// add from, to fields
	// BUG CORY
	fileS += ::write(Fd, &from, 4);
	fileS += ::write(Fd, &to, 4);

	// make the count of the number of bytes in the line
	// the next 2 bytes
	if( (nwrite = ::write(Fd, countPtr, 2)) != 2)
	{
#if ( DEBUG_PRINT >= ERRDEBUG )
		perror("FileLog::write - couldn't write count");
#endif
		return ERROR;
	}
	fileS += nwrite;

	// make the next the bytes the next buf_len of bytes
	// in the file
	nwrite = ::write(Fd, (char *)buf, buf_len);    
	if( nwrite > 0)
		bytesW += nwrite;
#if ( DEBUG_PRINT >= ERRDEBUG )
	else if ( nwrite < 0 ) {
		perror("FileLog::write - couldn't write buffer");
	}
#endif

	return nwrite;
}

int FileLog::write( int from, int to, const String& value) 
{
	return FileLog::write(from, to, value.c_str(), value.length());
}

int FileLog::write(int from, int to,  const Command& cmd) 
{
	uint8_t buf[CMD_MAX_SIZE];
	cmd.copyToBuf(buf);
	return FileLog::write(from, to, (char *)buf, cmd.size());
}

//-------------------------------------------------------------------- 
// Read Functions
// - the return value is not the total bytes read, but the number of
//   log bytes read.
// - if a line starting with -1 is read, then the tmTime value is 
//   updated and the next line is read to be returned
//-------------------------------------------------------------------- 
int FileLog::read( LogFileStruct & line)
{
	int nread = 0;
	char *timePtr = (char *)&line.timeOfDay;
	char *countPtr = (char *)&line.numOfBytes;
	//char *fromPtr = (char *)&line.from;
	//char *toPtr = (char *)&line.to;

	do {
		// read first 4 bytes, to get the time stamp
		if( (nread = ::read(Fd, timePtr,  4)) != 4 ) {
			// EOF
			if( nread == 0 )
				return 0;

			perror("FileLog::read() - time stamp");
			return ERROR;
		}

		if( line.timeOfDay == -1 ) {
			if( (nread = ::read(Fd, (char *)&tmTime,  sizeof(struct tm))) != sizeof(struct tm) ) {
				// EOF
				if( nread == 0 )
					return 0;

				perror("FileLog::read() - file time struct");
				return ERROR;
			}
			/*
				cout << "File Time:"
				<< " year=" << tmTime.tm_year + 1900
				<< " month=" << tmTime.tm_mon + 1
				<< " day=" << tmTime.tm_mday
				<< " hour=" << tmTime.tm_hour
				<< " min=" << tmTime.tm_min  
				<< " sec=" << tmTime.tm_sec << endl;
				*/
		}
		if( line.timeOfDay == -2 ) {
			if( (nread = ::read(Fd, (char *)&svn_ver,  sizeof(int))) != sizeof(int) ) {
				// EOF
				if( nread == 0 )
					return 0;

				perror("FileLog::read() - file version number ");
				return ERROR;
			}
			//cout	<< "\tsvn version=" << version << endl;
			if( svn_ver > 0 && compiled_ver != svn_ver ) {
				cout << "FileLog::read() - file version does not match compiled version" << endl
					<< "\tlog version=" << svn_ver << " compiled=" << compiled_ver << endl;
			}
		}

	} while( line.timeOfDay < 0 );

	// read next 4 bytes, to get the from field
	if( (nread = ::read(Fd, &line.from,  4)) != 4 ) {
		perror("FileLog::read() - number of bytes");
		return ERROR;
	}

	// read next 4 bytes, to get the to field
	if( (nread = ::read(Fd, &line.to,  4)) != 4 ) {
		perror("FileLog::read() - number of bytes");
		return ERROR;
	}

	// read next 2 bytes, to get the number of logged bytes
	if( (nread = ::read(Fd, countPtr,  2)) != 2 ) {
		// EOF
		if( nread == 0 )
			return 0;

		perror("FileLog::read() - number of bytes");
		return ERROR;
	}

	//cout << "line.timeOfDay=" << line.timeOfDay 
	//	<< " line.numOfBytes=" << line.numOfBytes << endl;

	if( line.bytes != NULL )
		delete [] line.bytes;
	line.bytes = NULL;

	line.bytes = new char[ line.numOfBytes ];

	// read next numOfBytes to get the logged bytes
	if( (nread = ::read(Fd, line.bytes,  line.numOfBytes)) != line.numOfBytes )
	{
		perror("FileLog::read() - logged bytes");
		delete [] line.bytes;
		line.bytes = NULL;

		return ERROR;
	}

	return nread;
}

// returns the number of lines read
int FileLog::read( LogFileStruct *line, size_t n)
{
	size_t i;
	int nread;
	for(i=0; i < n; i++) {
		nread=FileLog::read( line[i] );
		if( nread == ERROR )
			return ERROR;
		else if( nread == 0 )
			return i;
	}
	return i;
}
